package gui.batches;

import facades.ItemFacade;
import facades.ProductFacade;
import gui.common.Controller;
import gui.common.DataConverter;
import gui.inventory.ProductContainerData;
import gui.item.ItemData;
import gui.product.ProductData;

import java.util.ArrayList;
import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.Stack;
import java.util.Timer;
import java.util.TimerTask;

import coreModel.HIT;
import coreModel.Item;
import coreModel.ItemManager;
import coreModel.Product;
import coreModel.ProductManager;
import coreModel.StorageUnit;

/**
 * Super class for all the item batch controllers. This class has been created to avoid
 * duplicated code in the respective sub-classes for adding/removing/transferring.
 */
public abstract class ItemBatchController extends Controller implements Observer 
{
	public final int BARCODE_TIME = 250;
	
	static protected ItemFacade itemFacade = ItemFacade.getInstance();
	static protected ProductFacade productFacade = ProductFacade.getInstance();
	static protected ItemManager itemManager = HIT.getInstance().getItemManager();
	static protected ProductManager productManager = HIT.getInstance().getProductManager();
	
	protected ProductContainerData targetData;
	protected StorageUnit target;

	protected Stack<Command> undoStack;
	protected Stack<Command> redoStack;
	
	//added to make scanner default selected and to clear barcode when changed
	protected boolean useScanner=true;
	//added to implement scanner
	protected Timer timer;
	protected boolean notScheduled=true;
	
	/**
	 * Constructor.
	 * 
	 * @param view Reference to the associated item batch view.
	 * @param target Reference to the storage unit the controller will work on, or null if N/A.
	 */
	public ItemBatchController(ItemBatchView view, ProductContainerData target) 
	{
		super(view);

		this.targetData = target;
		if (target != null)
			this.target = (StorageUnit) target.getTag();

		undoStack = new Stack<Command>();
		redoStack = new Stack<Command>();
		
		getView().giveBarcodeFocus();
		getView().setUseScanner(true);
		
		timer = new Timer("barCode thread");
		
		construct();
	}
	
	/**
	 * Returns a reference to the view for this controller.
	 */
	@Override
	protected ItemBatchView getView() 
	{
		return (ItemBatchView)super.getView();
	}

	/**
	 * Loads data into the controller's view. Specifically, it populates the list of
	 * products and items in the view.
	 * 
	 *  {@pre None}
	 *  
	 *  {@post The controller has loaded data into its view}
	 */
	@Override
	protected void loadValues() 
	{
		ArrayList<ProductData> productsToLoad = new ArrayList<ProductData>();
		//for each command in undoStack, add the product to the list in not there already
		for (Command command : undoStack)
		{
			//look for an productData in productsToLoad matching the 
			// command's item's product's barcode
				
			ProductData productData = DataConverter.ConvertProduct(
					command.getItems().get(0).getProduct(), 
					command.getItems().get(0).getParentContainer());
			
			//set the count on the productData
			productData.setCount(Integer.toString(command.getItems().size()));
			
			//search (and update the count of) the list of ProductDatas
			boolean isFoundInList = false;
			for (ProductData listProductData : productsToLoad)
			{
				if (listProductData.getBarcode().equals(productData.getBarcode()))
				{
					isFoundInList = true;
					//increment the product's count within the list
					listProductData.setCount(Integer.toString(
							command.getItems().size()
							+ Integer.parseInt(listProductData.getCount())));
					break;
				}
			}
			//add the item to the list (if not already in the list)
			if (!isFoundInList)
				productsToLoad.add(productData);
		
			
		}
		//load the command's products (some conversion necessary)
		ProductData productsToLoadArray[] = new ProductData[productsToLoad.size()];
		productsToLoadArray = productsToLoad.toArray(productsToLoadArray);
		
		//collect state of product selection
		ProductData persistProduct = getView().getSelectedProduct();
		int productLocInArray = productsToLoad.indexOf(persistProduct);
		if (productLocInArray >= 0)
			persistProduct = productsToLoad.get(productLocInArray);
		
		getView().setProducts(productsToLoadArray);
		
		//revert state of product selection
		getView().selectProduct(persistProduct);
		
		loadItems(); //calls a private function to do the other half of the job
	}

	/**
	 * Sets the enable/disable state of all components in the controller's view.
	 * A component should be enabled only if the user is currently
	 * allowed to interact with that component.
	 * 
	 * {@pre None}
	 * 
	 * {@post The enable/disable state of all components in the controller's view
	 * have been set appropriately.}
	 */
	@Override
	protected void enableComponents() 
	{
		//enable the "<Command> Item" button only if the "Use Scanner" checkbox isn't selected
		// AND the "Item Barcode" field isn't empty.
		boolean isEmpty = (getView().getBarcode().length() == 0);
		getView().enableItemAction(!useScanner && !isEmpty);
		
		//enable undo/redo if their stacks aren't empty
		getView().enableUndo(!undoStack.isEmpty());
		getView().enableRedo(!redoStack.isEmpty());
	}

	/**
	 * This method is called when the "Item Barcode" field in this controller's
	 * item batch view is changed by the user.
	 */
	//@Override
	public abstract void barcodeChanged();
	
	/**
	 * This method is called when the "Use Barcode Scanner" setting in this controller's
	 * item batch view is changed by the user.
	 */
	//@Override
	public void useScannerChanged() 
	{
		if(getView().getUseScanner()!=useScanner)
		{
			getView().setBarcode("");
			useScanner=getView().getUseScanner();
		}
		enableComponents();
	}
	
	/**
	 * This method is called when the selected product changes
	 * in this controller's item batch view.
	 */
	//@Override
	public void selectedProductChanged() 
	{
		loadItems();
	}
	
	
	/**
	 * 
	 * Loads a list of items of the selected product into the view.
	 * These are items that have add/removed/transferred.
	 * This method is called by 'loadValues()'.
	 * 
	 */
	protected void loadItems()
	{
		ArrayList<ItemData> itemsToLoad = new ArrayList<ItemData>();
		
		//if there is a product selected, we can continue
		if (getView().getSelectedProduct() != null)
		{		
			Product p = (Product) getView().getSelectedProduct().getTag();
			//for all items matching that have been commanded (1)
			for (Command command : undoStack)
			{
				//if the item has its product selected (2), add it to the list
				if (p.equals(
						command.getItems().get(0).getProduct()))
				{
					
					for(int i=0;i<command.getItems().size();i++)
					{
						
						ItemData itemData = new ItemData(command.getItems().get(i));
						if (target != null) //this is a special case for removals, which have no target
							itemData.setStorageUnit(target.getName());
						itemsToLoad.add(itemData);
									
					}
				
				}
			}
		}
				
		

		//load the command's items (some conversion necessary)
		ItemData itemsToLoadArray[] = new ItemData[itemsToLoad.size()];
		itemsToLoadArray = itemsToLoad.toArray(itemsToLoadArray);
		
		//collect state of item selection
		ItemData persistItem = getView().getSelectedItem();
		int itemLocInArray = itemsToLoad.indexOf(persistItem);
		if (itemLocInArray >= 0)
			persistItem = itemsToLoad.get(itemLocInArray);
		
		getView().setItems(itemsToLoadArray);
		
		//revert state of item selection
		getView().selectItem(persistItem);
	}

	
	
	
	/**
	 * This method is called when the user clicks the "Redo" button
	 * in this controller's item batch view.
	 */
	//@Override
	public void redo() 
	{
		redoStack.peek().redo();
		undoStack.push(redoStack.pop());
		loadValues();
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in this controller's item batch view.
	 */
	//@Override
	public void undo() 
	{
		undoStack.peek().undo();
		redoStack.push(undoStack.pop());
		loadValues();
		enableComponents();
	}

	/**
	 * This method is called when the user clicks the "Done" button
	 * in this controller's item batch view.
	 */
	//@Override
	public void done() 
	{
		getView().close();
	}
	
	/**
	 * This method should be called when the model is changed by 
	 * an object this controller is observing.
	 */
	public void update(Observable obs, Object obj)
	{
		loadValues();
	}

		
	/**
	 * Private class for keeping track of commands (additions/transfers/removals) 
	 * we might undo/redo.
	 */
	public abstract class Command
	{
		protected Product addedProduct; //set to null if no product was added
		
		/**
		 * Create a new command from the needed model info
		 * 
		 * @param item the item the command will be performed on
		 */
		public ArrayList<Item> items = null;
		
		
		public ArrayList<Item> getItems(){
			return items;
		}
		
		
		/**
		 * An abstract class that needs to be implemented by the subclasses for proper
		 * functionality of add/remove/transfer. Should be implemented by the perspective
		 * controller classes.
		 */
		public void redo(){
			
		}
		
		/**
		 * An abstract class that needs to be implemented by the subclasses for proper
		 * functionality of add/remove/transfer. Should be implemented by the perspective
		 * controller classes.
		 */
		public void undo(){
			
		}
	}
	
	

	/** Private class for the barcode scanning timer in this controller's item batch view. 
	 * 
	 */
	protected abstract class BarcodeReader extends TimerTask
	{
		/** 
		 * This will be called when this timer runs out. 
		 */
		@Override
		public abstract void run();
	}
	
}


